using System;

namespace Fadd.Commands
{
    /// <summary>
    /// This is a bit more advanced command pattern. The regular command pattern
    /// ties the command to the the class that handles the command. This
    /// pattern removes that binding and makes the commands independent of
    /// the logic that executes it.
    /// <para>
    /// The pattern also allows multiple handlers for each command, and you
    /// can also add handlers for unhandled commands.
    /// </para>
    /// </summary>
    /// <example>
    /// // Map a handler to a command:
    /// distlight.Add(typeof(MyCommand), OnMyCommand);
    /// 
    /// // invoke my command:
    /// if (!distlight.Invoke(new MyCommand(myUserId)))
    ///   Console.WriteLine("No one handled the command!");
    /// 
    /// </example>
    public interface ICommandDispatcher
    {
        /// <summary>
        /// Add a command handler.
        /// </summary>
        /// <param name="type">Must be a class, an attribute.</param>
        /// <param name="handler">handler handling the command</param>
        /// <exception cref="ArgumentException">If handler have been added to that type already.</exception>
        /// <example>
        /// Program.Commands.Add(typeof(MyCommand), OnMyCommand);
        /// </example>
        void Add(Type type, CommandHandler handler);

        /// <summary>
        /// Remove a command handler.
        /// </summary>
        /// <param name="type">type to remove</param>
        /// <param name="handler">delegated that was mapped to the type.</param>
        void Remove(Type type, CommandHandler handler);

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="command">command to invoke.</param>
        /// <param name="ignoreMe">Handled that should not be invoked.</param>
        /// <returns>true if command was handled.</returns>
        [Obsolete("Used the version with the source argument instead.")]
        bool Invoke(Command command, CommandHandler ignoreMe);

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="command">command to invoke.</param>
        /// <returns>true if command was handled.</returns>
        [Obsolete("Used the version with the source argument instead.")]
        bool Invoke(Command command);

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">command to invoke.</param>
        /// <param name="ignoreMe">Handled that should not be invoked.</param>
        /// <returns>true if command was handled.</returns>
        bool Invoke(object source, Command command, CommandHandler ignoreMe);

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">command to invoke.</param>
        /// <returns>true if command was handled.</returns>
        bool Invoke(object source, Command command);

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        [Obsolete("Used the version with the source argument instead.")]
        IAsyncResult BeginInvoke(Command command, AsyncCallback callback, object state);

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="ignoreMe">Handler that should not receive the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        [Obsolete("Used the version with the source argument instead.")]
        IAsyncResult BeginInvoke(Command command, CommandHandler ignoreMe, AsyncCallback callback, object state);

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        IAsyncResult BeginInvoke(object source, Command command, AsyncCallback callback, object state);

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="ignoreMe">Handler that should not receive the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        IAsyncResult BeginInvoke(object source, Command command, CommandHandler ignoreMe, AsyncCallback callback, object state);

        /// <summary>
        /// Invoke this method when the command is complete, or if you want to wait
        /// on the command.
        /// </summary>
        /// <param name="res"></param>
        /// <returns></returns>
        Command EndInvoke(IAsyncResult res);

        /// <summary>
        /// Tells us if we have a handler for the specified type.
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>True if a handler have been registered otherwise false.</returns>
        bool Contains(Type type);

        /// <summary>
        /// Handler for unhandled commands.
        /// </summary>
        /// <remarks>returning true will make the command look like it was handled</remarks>
        event CommandHandler Unhandled;
    }
}